/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <vector>

#include "VerbParser.h"
#include "StringBuilder.h"
#include "Words.h"
#include "myassert.h"
#include "Table.h"
#include "Utils.h"

#define FOREACH_COMMAND for (CommandsConstIter iter = commands.begin(); iter != commands.end(); ++iter)

using namespace Chronos;
using namespace std;


namespace Chronos {

struct VerbAdjective {
    string adj;
    VerbParser::CommandType type;
    Command* cmd;
    VerbAdjective* next;
    VerbAdjective(const string& a, VerbParser::CommandType t, Command* c) : adj(a), type(t), cmd(c), next(0) {}
    ~VerbAdjective() { delete cmd; }
};

}


VerbParser::VerbParser(DefaultCommand& defaultCmd_)
    : defaultCmd(&defaultCmd_)
    , cmdCount(0)
{}


VerbParser::~VerbParser() {
    FOREACH_COMMAND {
        VerbAdjective* adj = iter->second;
        while (adj != 0) {
            VerbAdjective* next = adj->next;
            delete adj;
            adj = next;
        }
    }
    delete defaultCmd;
}


void VerbParser::addCommand(const string& verb, const string& adj, VerbParser::CommandType type, Command* cmd) {
    CommandsConstIter iter = commands.find(verb);
    if (iter == commands.end()) {
        commands[verb] = new VerbAdjective(adj, type, cmd);
    } else {
        VerbAdjective* verbadj = commands[verb]; 
        while (verbadj->next != 0) verbadj = verbadj->next;
        verbadj->next = new VerbAdjective(adj, type, cmd);
    }
}


void VerbParser::removeCommand(const string& verb, const string& adj) {
    CommandsIter iter = commands.find(verb);
    ASSERT_TRUE(iter == commands.end());
    VerbAdjective* current = iter->second;
    if (current->next == 0) {      // only 1 adjective
        ASSERT_TRUE(current->adj == adj);
        delete current;
        commands.erase(iter);
    } else {                            // multiple adjectives
        if (current->adj == adj) { // first one
            commands[verb] = current->next;
            delete current;
            return;
        }
        while (current->next != 0) {     // tail
            if (current->next->adj == adj) {
                VerbAdjective* toBeRemoved = current->next;
                current->next = current->next->next;
                delete toBeRemoved;
                return;
            }
            current = current->next;
        }
        ASSERT_FAIL();
    }
}


void VerbParser::showCommands(Session& session) const {
    Table table(3);
    string arrow = "->";
    FOREACH_COMMAND {
        VerbAdjective* adj = iter->second;
        while (adj) {
            table.addCell(iter->first);
            table.addCell(arrow);
            table.addCell(adj->cmd->getUsage());
            adj = adj->next;
        }
    }
    table.write(session.write());
}


void VerbParser::printStatus(StringBuilder& buffer) const {
    char buf[8];
    snprintf(buf, sizeof(buf), "%.2f", cmdCount / (float)Utils::getUptime());
    buffer << "  Parser: " << commands.size() << " verbs, parsed " << cmdCount << " commands (" << buf << "/sec) \n";
}


void VerbParser::parse(Session& session, const char* command) {
    ++cmdCount;
    Words words(command);
    if (words.getCount() == 0) return;

    CommandsConstIter iter = commands.find(words[0]);
    if (iter == commands.end()) {
        defaultCmd->execute(session, string(command));
        return;
    }

    VerbAdjective* matchingAdj = 0;
    int matchAdjPos = 0;
    VerbAdjective* adj = iter->second;
    while (adj) {
        if (adj->adj == "") {
            if (matchingAdj == 0) matchingAdj = adj;    // adj have preference
        } else {
            int pos = containsAdj(words, adj->adj);
            if (pos != 0) {
                matchingAdj = adj;
                matchAdjPos = pos;
                break;
            }
        }
        adj = adj->next;
    }

    if (matchingAdj == 0) {
        iter->second->cmd->printUsage(session);
    } else {
        if (!runVerb(session, words, matchingAdj, matchAdjPos)) matchingAdj->cmd->printUsage(session);
    }
}


int VerbParser::containsAdj(const Words& words, const string& adj) const {
    for (int i=1; i<words.getCount(); ++i) {
        if (words[i] == adj) return i;       
    }
    return 0;
}


bool VerbParser::runVerb(Session& session, const Words& words, VerbAdjective* adj, int adjPos) {
    string obj1;
    string obj2;
    switch(adj->type) {
        case VERB:
            if (words.getCount() != 1) return false;
            adj->cmd->execute(session);
            break;
        case VERB_ADJ:
            if (words.getCount() != 2) return false;
            adj->cmd->execute(session);
            break;
        case VERB_OBJ:
            if (words.getCount() < 2) return false;
            obj1 = words.subString(1, words.getCount()-1);
            adj->cmd->execute(session, obj1);
            break;
        case VERB_ADJ_OBJ:
            if (adjPos != 1) return false;
            if (words.getCount() <= 2) return false;
            obj1 = words.subString(adjPos + 1, words.getCount()-1);
            adj->cmd->execute(session, obj1);
            break;
        case VERB_OBJ_ADJ_OBJ:
            if (adjPos == 1) return false;
            if (words.getCount() < 4) return false;
            if (adjPos == words.getCount() -1) return false;
            obj1 = words.subString(1, adjPos - 1);
            obj2 = words.subString(adjPos + 1, words.getCount()-1);
            adj->cmd->execute(session, obj1, obj2);
            break;
		case VERB_OBJ_OBJ:
			if (words.getCount() < 3) return false;
			obj1 = words.subString(1, 1);
			obj2 = words.subString(2, words.getCount()-1);
            adj->cmd->execute(session, obj1, obj2);
            break;
        default:
    		ASSERT_FAIL();
    	break;
    }
    return true;
}

